package org.osite.xml;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import org.osite.util.Preconditions;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;

/**
 * A helper class to get arguments of a method of constructor from XML element's
 * attributes.
 */
class ArgumentsHelper {

    private final Class<?> paramType[];
    private final String defaultValue[];
    private final String attribute[];
    private final AttributeConverter<?> convertor[];
    private int offset = 0;

    public ArgumentsHelper(Annotation annotation[][], Class<?> paramType[],
            ConvertorProvider provider, int offset) throws ParseSetupException {
        Preconditions.checkArgument(offset <= annotation.length);
        this.offset = offset;
        XmlParam attributed[] = getAttributedAnnotations(annotation);
        this.paramType = Preconditions.checkNotNull(paramType);
        attribute = new String[paramType.length];
        defaultValue = new String[paramType.length];
        convertor = new AttributeConverter<?>[paramType.length];
        for (int i = offset, n = attributed.length; i < n; ++i) {
            attribute[i] = attributed[i].value();
            defaultValue[i] = attributed[i].preset();
            @SuppressWarnings("unchecked")
            Class<? extends AttributeConverter> cType = attributed[i]
                    .convertor();
            try {
                convertor[i] = cType.isInterface() ? provider
                        .getAttributeConverter(paramType[i]) : ParseUtil
                        .newInstance(cType);
                if (convertor[i] == null) {
                    throw new ParseSetupException("no convertors for type: "
                            + paramType[i]);
                }
            } catch (PasreReflectionException e) {
                throw new ParseSetupException("cannot create convertor: "
                        + cType, e);
            }
        }
    }

    public ArgumentsHelper(Constructor<?> ctor, ConvertorProvider provider)
            throws ParseSetupException {
        this(ctor.getParameterAnnotations(), ctor.getParameterTypes(), 
                provider, 0);
    }

    public ArgumentsHelper(Method method, ConvertorProvider provider)
            throws ParseSetupException {
        this(method, provider, 0);
    }

    public ArgumentsHelper(Method method, ConvertorProvider provider, 
            int offset) throws ParseSetupException {
        this(method.getParameterAnnotations(), method.getParameterTypes(),
                provider, offset);
    }

    private XmlParam[] getAttributedAnnotations(
            Annotation annotations[][]) throws ParseSetupException {
        XmlParam[] result = new XmlParam[annotations.length];
        for (int i = offset, n = annotations.length; i < n; ++i) {
            for (Annotation candidate : annotations[i]) {
                if (candidate instanceof XmlParam) {
                    result[i] = (XmlParam) candidate;
                    break;
                }
            }
            if (result[i] == null) {
                throw new ParseSetupException(
                        "no 'Attributed' annotation found in param[" 
                        + i + "]");
            }
        }
        return result;
    }
    
    public Object[] getArguments(Element element, ParseContext context)
            throws XmlParseException {
        
        Object args[] = new Object[attribute.length];
        for (int i = offset, n = args.length; i < n; ++i) {
            Attr attr = element.getAttributeNode(attribute[i]);
            try {
                if (attr != null) {
                    context.setHandled(attr);                    
                    args[i] = convertor[i].convert(
                            paramType[i], attr.getValue(), context);
                } else if (!XmlParam.REQUIRED.equals(defaultValue[i])) {
                    args[i] = convertor[i].convert(paramType[i],
                            defaultValue[i], context);
                } else {
                    throw new XmlParseException("required attribute missing: "
                            + attribute[i], element);
                }
            } catch (XmlParseException e) {
                e.setWhere((attr == null) ? element : attr);
                throw e;
            }
        }
        return args;
    }
}
